package mutation;


import java.lang.reflect.Array;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.ArrayList;
import ga.*;
import operations.*;
import util.DebugConfig;
import util.GAConfig;
import util.Log;
import util.Randomness;
import mdr.RecommendGenerator;
import model.*;
import instrumentation.Data;
import instrumentation.Value;
import instrumentation.Condition;
import instrumentation.ConditionSwitch;
import instrumentation.Constant;
import instrumentation.Virtualinvoke;
import instrumentation.ConstantString;
public class MDRChangeInputValue extends MutationStrategy {
	@Override
	public Individual mutate(Individual individual) {
		if(DebugConfig.ChangeInputValueClass){
			System.out.println("------- Recommended Mutation Change Value Strategy ---------------"); 
			System.out.println("--------------------individual1----------------------");
			//System.out.println(individual.toString()+ " size:"+ individual.getSize());
			System.out.println("\t size: "+ individual.getSize());
			System.out.println("-----------------------------------------------------");
		}
		long totalMutation=this.gaInfo.getTotalMutationChange();

		// select random target to assign the mutations of the non-covered bracnhes.

			RecommendGenerator recommendGenerator=this.gaInfo.getRecommendGenerator();
			Integer trgtBR=-1;
			if(GAConfig.TargetHitBranches)
				trgtBR=MutationFactory.getHitTargetToCover(this.gaInfo);
			else
				trgtBR=MutationFactory.getTargetToCover(this.gaInfo);
			if(trgtBR==-1) return individual;
	//		for(trgtBR=1;trgtBR<20;trgtBR+=1){
	//		trgtBR=53;
				Condition c=recommendGenerator.getCondition(trgtBR);
//				System.out.println("MDRChangeInputValue trgtBR: "+ trgtBR);//+ " " +recommendGenerator.getBranchWithData(trgtBR));

				List<StatementKind>  BRRelatedStatementList=recommendGenerator.getRecommendMethodsBranch(trgtBR);
//				System.out.println("BRRelatedStatementList trgtBR: [ " + trgtBR + " ] " +BRRelatedStatementList);
	
				if(BRRelatedStatementList.size()==0)
					return individual;
	
				//if(!(((Data)v1).isField()|| ((Data)v1).isParam())) return individual;

//			}
		Individual mutatedIndividual=individual.clone();
		
		// Change Initial Constructions
		try {
			List<Operation> cons = mutatedIndividual.getInitialConstructors();
			for (int i = 0; i < cons.size(); i++) {
				if (!getMutationFlag(cons.size()))
					continue;
				Operation mutateOp=null;
				Operation op = cons.get(i);
//				 System.out.println(i+ " ChangeInputValueClass: "+ op.toString());
				if (op instanceof CreateObject) {
					 mutateOp = mutantConstructoParams((CreateObject) op, mutatedIndividual,trgtBR, BRRelatedStatementList);

				} else if (op instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant((AssignConstant) op, mutatedIndividual);
					}
				if(mutateOp==null) continue;
				cons.set(i, mutateOp);

			}

			List<Operation> ops = mutatedIndividual.getStatements();
			for (int i = 0; i < ops.size(); i++) {
				Operation op = ops.get(i);
				Operation mutateOp=null;
				if (!getMutationFlag(BRRelatedStatementList.size()))
					continue;
				if(ops.get(i) instanceof CreateArray)
					 mutateOp = mutantArray((CreateArray) op,
								mutatedIndividual);

				if (ops.get(i) instanceof InvokeMethod) {				
					 mutateOp = mutantMethodParams((InvokeMethod) op,mutatedIndividual,trgtBR, BRRelatedStatementList);
				} else if (ops.get(i) instanceof CreateObject) {
					 mutateOp = mutantConstructoParams((CreateObject) op, mutatedIndividual,trgtBR, BRRelatedStatementList);

				} else if (ops.get(i) instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant((AssignConstant) op, mutatedIndividual);
				}
				if(mutateOp==null) continue;
				totalMutation++;
				ops.set(i, mutateOp);

			}
		} catch (Throwable e){
			System.err.println("MDRChangeInputValue:" + e .getMessage());
		}	
		if(DebugConfig.ChangeInputValueClass){
			Log.log("--------------------individual1----------------------");
			Log.log(individual.toString()+ " size:"+ individual.getSize());
			Log.log("--------------------mutatedIndividual----------------------");
			Log.log(mutatedIndividual.toString()+ " size:"+ mutatedIndividual.getSize());
		}
		this.gaInfo.setTotalMutationChange(totalMutation);

		return mutatedIndividual;
	}
	private Operation mutantArray(CreateArray op,Individual individual) throws Throwable {
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		 CreateArray array=((CreateArray)op);
		   Object theArray = Array.newInstance(array.getElementType(), array.getLength());
		   List<Object> inputList=array.getInputVariables();
		    for (int i = 0; i < array.getLength(); i++)
		    	if(Filter.isPrimitiveOrStringType(array.getElementType()))
		    		inputList.set(i, ValuesGenerator.modifyPrimitiveValue(array.getElementType(),inputList.get(i)));
		    	else
		    		inputList.set(i, Operation.generateReference(array.getElementType(),TestCluster));

		return op;
	}
	private Operation mutantMethodParams(InvokeMethod op,Individual individual,int trgtBR,List<StatementKind> BRRelatedStatementList) throws Throwable {
//		System.out.println("mutantMethodParams " + op.toString());
//		if(op.toString().contains("getDescendants"))
//			System.out.println("mutantMethodParams " + op.toString());
		try{
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		List<Class<?>>replacementObjectParam= op.getReplacementObjectParam();
		StatementKind m = op.getMethod();
		List<Class<?>> paramsTpe = m.getInputTypes();
		List<Object> oldValues=op.getValues();
		List<Object> newValues=new LinkedList<Object>();

		//Change return reference
			String _return = (((MethodDeclared) m).isVoid()) ? null : Operation.getReference(((MethodDeclared) m).getMethod().getReturnType().getName(), TestCluster.getRefFactory());
			if(_return==null&&!((MethodDeclared) m).isVoid()){
				Class<?> rType=Operation.generateReference(((MethodDeclared) m).getMethod().getReturnType(), TestCluster);
				if(rType!=null){
					if(!rType.getName().contains("java."))
					if(!rType.getName().startsWith("sun"))
					if(!rType.getName().contains("$")) 
					if(!Filter.isPrimitiveOrStringType(rType)){
									_return=Operation.getReference(rType.getName(), refFactory);//clz2.toString();	
						}
				}
			}
			op.setReturn(_return);
		//Cahneg parameters
		boolean commitedChange=false;
		for(int i = 0; i < paramsTpe.size(); i++) {
			Class<?> paramTpe=paramsTpe.get(i);
				if (Filter.isPrimitiveOrStringType(paramTpe))
					newValues.add(ValuesGenerator.modifyPrimitiveValue(paramTpe,oldValues.get(i)));
				else{
					if(Filter.isObjectType(paramTpe)){
						paramTpe=Operation.generateReference(paramTpe, TestCluster);
						if (Filter.isPrimitiveOrStringType(paramTpe))
							newValues.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
						else{
							if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
								newValues.add(null);
							else
								newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
						}
					}
					else{
						paramTpe=Operation.generateReference(paramTpe, TestCluster);
						if(paramTpe==null)
							return op;
						if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
							newValues.add(null);
						else
							newValues.add(Operation.getReference(paramTpe.getName(), refFactory));
					}
				}
				replacementObjectParam.set(i,paramTpe);
				commitedChange=true;
		}
		if(commitedChange)
			op.setValues(newValues);
	} catch (Throwable e){
		System.err.println("MDRChangeInputValue.mutantMethodParams :" +op + "  "+  e .getMessage());
	}
		return op;
	}
	private Operation mutantConstructoParams(CreateObject op,Individual individual,int trgtBR, List<StatementKind> BRRelatedStatementList) throws Throwable {
//		System.out.println("random.mutantMethodParams " + op.toString());
		try{
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();

		StatementKind cons = op.getConstructor();
		List<Class<?>> paramsTpe = cons.getInputTypes();
		List<Object> oldValues=op.getValues();
		List<Object> newValues=new LinkedList<Object>();

		boolean commitedChange=false;
		for(int i = 0; i < paramsTpe.size(); i++) {
			Class<?> t=paramsTpe.get(i);
				if (Filter.isPrimitiveOrStringType(t))
					oldValues.set(i,ValuesGenerator.modifyPrimitiveValue(t,oldValues.get(i)));
				else{

					if(Filter.isObjectType(t)){
						t=Operation.generateMDRReference(t, BRRelatedStatementList, TestCluster);
						if(t==null) // relatedStatemt dont contains constructors;
							t=Operation.generateReference(paramsTpe.get(i),TestCluster);
						if (Filter.isPrimitiveOrStringType(t))
							oldValues.set(i,ValuesGenerator.generatePrimitiveValue(t));
						else{
								oldValues.set(i,Operation.getReference(t.getName(), refFactory));
						}
					}
					else{
						t=Operation.generateReference(t, TestCluster);
						if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
							oldValues.set(i,null);
						else{
							if(t!=null)
								oldValues.set(i,Operation.getReference(t.getName(), refFactory));
							else{
								oldValues.set(i,Operation.getReference(paramsTpe.get(i).getName(), refFactory));
							}
						}
					}						
				}
				commitedChange=true;
		}
		newValues.addAll(oldValues);
		op.setValues(newValues);
		op.addInfo(new OperationResult());
	} catch (Throwable e){
		System.err.println("MDRChangeInputValue.mutantConstructoParams:" + op+ " "+ e .getMessage());
	}
		return op;
	}
			private Operation mutantAssignConstant(AssignConstant op,Individual individual) throws Throwable {
				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, CUT>  cluster= TestCluster.getCluster();
				String cName= ((AssignConstant) op).getDeclaringClass().getName();
				CUT cut =cluster.get(cName) ;
				op = AssignConstant.generate(cut,TestCluster);
				return op;
			}
	@Override
	public boolean getMutationFlag(int size) {
		float mutationRate=1.0f/size;
		if(GAConfig.mutation_rate!=0f)
			mutationRate=GAConfig.mutation_rate;
		if(ValuesGenerator.getBoolean(mutationRate))
			return true;
		return false;

	}
}